/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/ces-loader.hh"
#include "carina/mesh.hh"
#include "carina/common/ces-file.hh"

#include <fstream>
#include <stack>
#include <cstring>

namespace Carina
{
struct true_type;
struct false_type;

template<class T>
struct is_container
{
    typedef false_type value_type;
    const static bool value = false;
};

template<template<class U, class A> class TCont, class T, class TAlloc>
struct is_container< TCont<T, TAlloc> >
{
    typedef true_type value_type;
    const static bool value = true;
};

// TODO: Non-DynamicArray
template<class T1, class T2>
void MoveAttribute(CESAttribute& attr, Material& mat)
{
    T1 val;
    attr.get(val);
    mat.set(attr.getName(), *reinterpret_cast<T2*>(&val));
}

template<class T1, class T2>
void MoveAttribute(CESAttribute& attr, AttributeList& attr_list)
{
    T1 val;
    attr.get(val);
    attr_list.setAttributeValue(attr.getName(), *reinterpret_cast<T2*>(&val));
}

template<class T1, class T2>
void MoveAttribute(AttributeList& attr_list, size_t idx, CESAttributeContainer& ces_attr)
{
    T1 val;
    attr_list.getAttributeValue(idx, val);
    ces_attr.setAttribute(attr_list.getAttribute(idx)->getName(), *reinterpret_cast<T2*>(&val));
}

ModelPtr CESLoader::loadModel(CESEntity& ces_model, CESGeometrySection& ces_geom, ResourceManager& res_mgr)
{
    string              mesh_name,
                        model_name,
                        col_construction;
    AABBHierarchyPtr    colh;
    if(!ces_model.getAttribute("Rendering mesh", mesh_name) ||
        !ces_model.getAttribute("Name", model_name))
        return ModelPtr();
    if(ces_model.getAttribute("Collision hierarchy", col_construction))
    {
        if(col_construction == "generate")
        {
            DynamicArray<uint32> indices;
            DynamicArray<float> vertices;
            CESMeshReference* mesh_ref = ces_geom.getMeshReference(mesh_name);
            if(!mesh_ref)
                return ModelPtr();
            CESMeshInfoPtr mesh_info = mesh_ref->getMeshInfo();
            if(mesh_info->isInPlace())
            {
                CESMeshInPlace& mesh_ip = static_cast<CESMeshInPlace&>(*mesh_info);
                CEMLoader::loadTriangles(mesh_ip.getMesh(), vertices, indices);
            }
            else
            {
                CESMeshFromFile& mesh_ff = static_cast<CESMeshFromFile&>(*mesh_info);
                string filename = mesh_ff.getFileName();
                MeshLoaderPtr mesh_loader = res_mgr.getMeshFileLoader(filename);
                if(!mesh_loader)
                    THROW_EXCEPTION("Unknown mesh format");
                mesh_loader->loadTriangles(res_mgr.getResource(filename), vertices, indices);
            }
            VertexData v;
            v.data = vertices.get();
            colh = make_aligned_shared<AABBHierarchy>();
            colh->buildMinSum(v, vertices.size()/3, indices.get(), indices.size());
        }
        else if(col_construction != "none")
            return ModelPtr();
    }

    return make_aligned_shared<Model>(res_mgr.getMesh(mesh_name), colh);
}

bool CESLoader::loadGeometry(CESGeometrySection& ces_geom, ResourceManager& res_mgr)
{
    for(size_t i = 0; i < ces_geom.getMeshRefCount(); ++i)
    {
        MeshPtr mesh;
        CESMeshReference& mesh_ref = ces_geom.getMeshReference(i);
        CESMeshInfoPtr mesh_info = mesh_ref.getMeshInfo();
        string mesh_name = mesh_ref.getMeshName();
        if(mesh_info->isInPlace())
        {
            mesh = CEMLoader::loadMesh(static_cast<CESMeshInPlace*>(mesh_info.get())->getMesh(), res_mgr);
            if(!mesh)
                return false;
            res_mgr.registerMesh(mesh_name, mesh);
        }
        else
        {
            CESMeshFromFile& ces_mff = *static_cast<CESMeshFromFile*>(mesh_info.get());
            string filename = ces_mff.getFileName();
            mesh = res_mgr.getMesh(filename);
            res_mgr.registerMesh(mesh_name, mesh);
        }
    }
    return true;
}

bool CESLoader::loadEntities(CESEntitySection& ces_ent, CESGeometrySection& ces_geom, ResourceManager& res_mgr, const WorldManagerPtr& wmgr)
{
    for(size_t i = 0; i < ces_ent.getEntityCount(); ++i)
    {
        string model_name;
        CESEntity& ent = ces_ent.getEntity(i);
        ModelPtr model(loadModel(ent, ces_geom, res_mgr));
        if(!model || !ent.getAttribute("Name", model_name))
            return false;
        wmgr->registerModel(model_name, model);
    }
    return true;
}

bool CESLoader::loadResources(CESFile& ces_file, ResourceManager& res_mgr, const WorldManagerPtr& wmgr)
{
    CESGeometrySection& ces_geom = ces_file.getGeometrySection();
    CESEntitySection& ces_ent = ces_file.getEntitySection();
    return loadGeometry(ces_geom, res_mgr) && loadEntities(ces_ent, ces_geom, res_mgr, wmgr);
}

struct CESLoaderStack
{
    TransformNodePtr    parent;
    CESNode&            ces_node;
    CESLoaderStack(const TransformNodePtr& _parent, CESNode& _cesnode)
        :   parent(_parent),
            ces_node(_cesnode) {}
};

bool CESLoader::load(const string& filename, ResourceManager& res_mgr, const LightingManagerPtr& lmgr, const NodeManagerPtr& nmgr, const WorldManagerPtr& wmgr, const ScenePtr& scene)
{
    CESFile     ces_file;
    if(!ces_file.open(filename))
        return false;
    
    if(!loadResources(ces_file, res_mgr, wmgr))
        return false;

    CESGlobalLightSection& glsection = ces_file.getGlobalLightSection();
    Vector3 amb;
    glsection.getAmbientLight(*reinterpret_cast<vec3_t*>(&amb));
    scene->setAmbientLight(amb);
    for(size_t i = 0; i < glsection.getGlobalLightCount(); ++i)
    {
        GlobalLightSourcePtr gls = loadGlobalLightSource(glsection.getGlobalLight(i), lmgr);
        if(!gls)
            return false;
        scene->addGlobalLightSource(gls);
    }

    std::stack<CESLoaderStack> stack;
    CESNode& root = ces_file.getRoot();
    for(size_t i = 0; i < root.getChildrenCount(); ++i)
        stack.push(CESLoaderStack(scene->getRoot(), root.getChild(i)));
    while(!stack.empty())
    {
        string node_type;
        CESNode& ces_node = stack.top().ces_node;
        TransformNodePtr parent = stack.top().parent;
        stack.pop();
        NodePtr node_ptr = loadNode(ces_node, nmgr);
        if(!node_ptr)
            return false;
        parent->addChild(node_ptr);
        if(!ces_node.attributes().getAttribute("Node type", node_type))
            return false;
        if(node_type == "Transform node")
            for(size_t i = 0; i < ces_node.getChildrenCount(); ++i)
                stack.push(CESLoaderStack(static_pointer_cast<TransformNode>(node_ptr), ces_node.getChild(i)));
    }

    return true;
}

NodePtr CESLoader::loadNode(CESNode& ces_node, const NodeManagerPtr& nmgr)
{
    string  node_type,
            creation_data;
    CESAttributeContainer& ces_attr = ces_node.attributes();
    if(!ces_attr.getAttribute("Node type", node_type))
        return NodePtr();
    ces_attr.getAttribute("Creation data", creation_data);
    NodePtr node_ptr = nmgr->create(node_type, creation_data);
    if(!node_ptr)
        return NodePtr();
    ConfigurablePtr node_conf = node_ptr->getConfigurable();
    if(!CESLoader::transferCESToAttributes(ces_attr, *node_conf))
        return NodePtr();

    return node_ptr;
}

GlobalLightSourcePtr CESLoader::loadGlobalLightSource(CESAttributeContainer& ces_attr, const LightingManagerPtr& lmgr)
{
    string lstype;
    if(!ces_attr.getAttribute("Light source type", lstype))
        return GlobalLightSourcePtr();
    LightSourcePtr lsource = lmgr->create(lstype);
    if(!lsource->isGlobalLightSource())
        return GlobalLightSourcePtr();
    if(!CESLoader::transferCESToAttributes(ces_attr, *lsource))
        return GlobalLightSourcePtr();
    return static_pointer_cast<GlobalLightSource>(lsource);
}

bool CESLoader::transferCESToAttributes(CESAttributeContainer& ces_attr, Configurable& conf)
{
	auto attr_list = GetAttributeList(conf);
    for(size_t i = 0; i < ces_attr.getAttributeCount(); ++i)
    {
        CESAttribute& attr = ces_attr.getAttribute(i);
        string attr_name = attr.getName();
        if(attr_name != "Node type" && attr_name != "Creation data" && attr_name != "Light source type")
        {
            switch(attr.getType())
            {
            case CES_ARRAY:
            {
                switch(attr.getArrayType())
                {
                case CES_FLOAT: MoveAttribute< DynamicArray<float>, DynamicArray<float> >(attr, *attr_list); break;
                case CES_INT: MoveAttribute< DynamicArray<int32>, DynamicArray<int32> >(attr, *attr_list); break;
                //case CES_SHORT: MoveAttribute< DynamicArray<int16>, DynamicArray<int16> >(attr, *attr_list); break;
                //case CES_BYTE: MoveAttribute< DynamicArray<int8>, DynamicArray<int8> >(attr, *attr_list); break;
                case CES_UINT: MoveAttribute< DynamicArray<uint32>, DynamicArray<uint32> >(attr, *attr_list); break;
                //case CES_USHORT: MoveAttribute< DynamicArray<uint16>, DynamicArray<uint16> >(attr, *attr_list); break;
                //case CES_UBYTE: MoveAttribute< DynamicArray<uint8>, DynamicArray<uint8> >(attr, *attr_list); break;
                case CES_VECTOR2: MoveAttribute< DynamicArray<vec2_t>, DynamicArray<Vector2> >(attr, *attr_list); break;
                case CES_VECTOR3: MoveAttribute< DynamicArray<vec3_t>, DynamicArray<Vector3> >(attr, *attr_list); break;
                case CES_VECTOR4: MoveAttribute< DynamicArray<vec4_t>, DynamicArray<Vector4> >(attr, *attr_list); break;
                case CES_QUATERNION: MoveAttribute< DynamicArray<quat_t>, DynamicArray<Quaternion> >(attr, *attr_list); break;
                case CES_DUAL_QUATERNION: MoveAttribute< DynamicArray<dquat_t>, DynamicArray<DualQuaternion> >(attr, *attr_list); break;
                // case CES_MATRIX3: MoveAttribute< DynamicArray<dquat_t>, DynamicArray<Matrix3> >(attr, *attr_list); break; // TODO: Matrix3
                case CES_MATRIX4:
                {
                    DynamicArray<mat4_t> arr1;
                    attr.get(arr1);
                    DynamicArray<Matrix4> arr2(arr1.size());
                    memcpy(arr2.get(), arr1.get(), sizeof(mat4_t)*arr1.size());
                    attr_list->setAttributeValue(attr.getName(), arr2);
                }
                default:
                    return false;
                }
            } break;
            case CES_FLOAT: MoveAttribute<float, float>(attr, *attr_list); break;
            case CES_INT: MoveAttribute<int32, int32>(attr, *attr_list); break;
            //case CES_SHORT: MoveAttribute<int16, int16>(attr, *attr_list); break;
            //case CES_BYTE: MoveAttribute<int8, int8>(attr, *attr_list); break;
            case CES_UINT: MoveAttribute<uint32, uint32>(attr, *attr_list); break;
            //case CES_USHORT: MoveAttribute<uint16, uint16>(attr, *attr_list); break;
            //case CES_UBYTE: MoveAttribute<uint8, uint8>(attr, *attr_list); break;
            case CES_VECTOR2: MoveAttribute<vec2_t, Vector2>(attr, *attr_list); break;
            case CES_VECTOR3: MoveAttribute<vec3_t, Vector3>(attr, *attr_list); break;
            case CES_VECTOR4: MoveAttribute<vec4_t, Vector4>(attr, *attr_list); break;
            case CES_QUATERNION: MoveAttribute<quat_t, Quaternion>(attr, *attr_list); break;
            case CES_DUAL_QUATERNION: MoveAttribute<dquat_t, DualQuaternion>(attr, *attr_list); break;
            //case CES_MATRIX3: MoveAttribute<mat3_t, Matrix3>(attr, attr_list); break; // TODO: Matrix3
            case CES_MATRIX4:
            {
                mat4_t mat1;
                attr.get(mat1);
                Matrix4 mat2(*mat1.elem);
                attr_list->setAttributeValue(attr.getName(), mat2);
            } break;
            default:
                return false;
            }
        }
    }
    return true;
}

bool CESLoader::transferAttributesToCES(Configurable& conf, CESAttributeContainer& ces_attr)
{
	auto attr_list = GetAttributeList(conf);
    for(size_t i = 0; i < attr_list->getAttributeCount(); ++i)
    {
        AttributePtr attr = attr_list->getAttribute(i);
        if(attr->isContainer())
        {
            switch(attr->getType())
            {
            case CE_UVT_FLOAT: MoveAttribute< DynamicArray<float>, DynamicArray<float> >(*attr_list, i, ces_attr); break;
            case CE_UVT_VEC2: MoveAttribute< DynamicArray<Vector2>, DynamicArray<vec2_t> >(*attr_list, i, ces_attr); break;
            case CE_UVT_VEC3: MoveAttribute< DynamicArray<Vector3>, DynamicArray<vec3_t> >(*attr_list, i, ces_attr); break;
            case CE_UVT_VEC4: MoveAttribute< DynamicArray<Vector4>, DynamicArray<vec4_t> >(*attr_list, i, ces_attr); break;
            case CE_UVT_INT: MoveAttribute< DynamicArray<int32>, DynamicArray<int32> >(*attr_list, i, ces_attr); break;
            //case CE_UVT_IVEC2: break;
            //case CE_UVT_IVEC3: break;
            //case CE_UVT_IVEC4: break;
            case CE_UVT_UINT: MoveAttribute< DynamicArray<uint32>, DynamicArray<uint32> >(*attr_list, i, ces_attr); break;
            //case CE_UVT_UVEC2: break;
            //case CE_UVT_UVEC3: break;
            //case CE_UVT_UVEC4: break;
            //case CE_UVT_BOOL: MoveAttribute< DynamicArray<bool>, DynamicArray<bool> >(*attr_list, i, ces_attr); break;
            //case CE_UVT_BVEC2: break;
            //case CE_UVT_BVEC3: break;
            //case CE_UVT_BVEC4: break;
            //case CE_UVT_MAT2: break;
            //case CE_UVT_MAT3: break;
            case CE_UVT_MAT4: MoveAttribute< DynamicArray<Matrix4>, DynamicArray<mat4_t> >(*attr_list, i, ces_attr); break;
            //case CE_UVT_MAT2x3: break;
            //case CE_UVT_MAT2x4: break;
            //case CE_UVT_MAT3x2: break;
            //case CE_UVT_MAT3x4: break;
            //case CE_UVT_MAT4x2: break;
            //case CE_UVT_MAT4x3: break;
            default:
                return false;
            }
        }
        else
        {
            switch(attr->getType())
            {
            case CE_UVT_FLOAT: MoveAttribute<float, float>(*attr_list, i, ces_attr); break;
            case CE_UVT_VEC2: MoveAttribute<Vector2, vec2_t>(*attr_list, i, ces_attr); break;
            case CE_UVT_VEC3: MoveAttribute<Vector3, vec3_t>(*attr_list, i, ces_attr); break;
            case CE_UVT_VEC4: MoveAttribute<Vector4, vec4_t>(*attr_list, i, ces_attr); break;
            case CE_UVT_INT: MoveAttribute<int32, int32>(*attr_list, i, ces_attr); break;
            //case CE_UVT_IVEC2: break;
            //case CE_UVT_IVEC3: break;
            //case CE_UVT_IVEC4: break;
            case CE_UVT_UINT: MoveAttribute<uint32, uint32>(*attr_list, i, ces_attr); break;
            //case CE_UVT_UVEC2: break;
            //case CE_UVT_UVEC3: break;
            //case CE_UVT_UVEC4: break;
            //case CE_UVT_BOOL: MoveAttribute<bool, bool>(*attr_list, i, ces_attr); break;
            //case CE_UVT_BVEC2: break;
            //case CE_UVT_BVEC3: break;
            //case CE_UVT_BVEC4: break;
            //case CE_UVT_MAT2: break;
            //case CE_UVT_MAT3: break;
            case CE_UVT_MAT4: MoveAttribute<Matrix4, mat4_t>(*attr_list, i, ces_attr); break;
            //case CE_UVT_MAT2x3: break;
            //case CE_UVT_MAT2x4: break;
            //case CE_UVT_MAT3x2: break;
            //case CE_UVT_MAT3x4: break;
            //case CE_UVT_MAT4x2: break;
            //case CE_UVT_MAT4x3: break;
            default:
                return false;
            }
        }
    }
    return true;
}

MeshPtr CEMLoader::load(const string& filename, ResourceManager& res_mgr)
{
    return CEMLoader::loadMesh(filename, res_mgr);
}

// TODO: Data Layout
void CEMLoader::loadTriangles(const string& filename, DynamicArray<float>& vertices, DynamicArray<uint32>& indices)
{
    CEMFile         cem_file;
    if(!cem_file.open(filename))
        THROW_EXCEPTION("an error has occurred while loading the data from the following mesh file: " + filename);
    CEMMesh&        cem_mesh = cem_file.getMesh();
    loadTriangles(cem_mesh, vertices, indices);
}

void CEMLoader::loadTriangles(const CEMMesh& cem_mesh, DynamicArray<float>& vertices, DynamicArray<uint32>& indices)
{
	auto&		 cem_point_data = cem_mesh.getPointData();
	auto&		 cem_indices = cem_mesh.getIndices();
    size_t       stride = cem_mesh.hasSkeleton() ? ces_skinned_size_float : ces_static_size_float,
				 raw_total = cem_point_data.size(),
                 vert_total = raw_total / stride;
    auto point_data = cem_mesh.getPointData();
	const float* raw_data = &point_data.front();
    indices.assign(&cem_indices.front(), cem_indices.size());
    vertices.resize(3 * vert_total);
    for(size_t i = 0; i < vert_total; ++i)
    {
        vertices[i*3] = raw_data[i*stride];
        vertices[i*3 + 1] = raw_data[i*stride + 1];
        vertices[i*3 + 2] = raw_data[i*stride + 2];
    }
}

MeshPtr CEMLoader::loadMesh(const string& filename, ResourceManager& res_mgr)
{
    CEMFile cem_file;
    if(!cem_file.open(filename))
        THROW_EXCEPTION("an error has occurred while loading the following mesh file: " + filename);
    return loadMesh(cem_file.getMesh(), res_mgr);
}

void CEMLoader::saveMesh(const MeshPtr& mesh, const string& filename)
{
    assert(false);
}

MeshPtr CEMLoader::loadMesh(CEMMesh& ces_mesh, ResourceManager& res_mgr)
{
    Skeleton skeleton;
    DynamicArray<Animation> animations;
    if(ces_mesh.hasSkeleton())
    {
        CESSkeletonPtr skel = ces_mesh.getSkeleton();
        skeleton.setCount(skel->getBoneCount(), skel->getNodeCount());
        skeleton.setRoot(skel->getRoot());
        for(size_t i = 0; i < skeleton.getNodeCount(); ++i)
        {
            CESBone& node = skel->getNode(i);
            DualQuaternion  transform(*reinterpret_cast<DualQuaternion*>(node.getTransform())),
                            inv_bind_transform(*reinterpret_cast<DualQuaternion*>(node.getInvTransform()));
            skeleton.getBone(i).setInitial(transform);
            skeleton.getBone(i).setInvBindTransform(inv_bind_transform);
            for(size_t j = 0; j < node.getChildrenCount(); ++j)
                skeleton.getBone(i).addChild(node.getChild(j));
        }

        KeyFrame empty_keyframe;
        size_t anim_count = ces_mesh.getAnimations().getCount();
        empty_keyframe.TransformQuats.resize(ces_mesh.getSkeleton()->getNodeCount());
        for(size_t i = 0; i < anim_count; ++i)
        {
            CESAnimationPtr ces_anim = ces_mesh.getAnimations().getAnimation(i);
            
            size_t keyframes = ces_anim->getKeyframesCount();
            AnimationData anim_data(keyframes, empty_keyframe);
            for(size_t j = 0; j < keyframes; ++j)
            {
                anim_data[j].Time = ces_anim->getKeyframeTime(j);
                for(size_t k = 0; k < ces_anim->getNodeCount(); ++k)
                    anim_data[j].TransformQuats[k] = *reinterpret_cast<DualQuaternion*>(ces_anim->getNode(j, k).getTransform());
            }
            Animation anim(ces_anim->getName(), anim_data);
            animations.push_back(anim);
        }
    }

    SubmeshArray sm_arr;
    for(size_t i = 0; i < ces_mesh.getSubmeshCount(); ++i)
    {
        CESSubmesh& ces_sm = ces_mesh.getSubmesh(i);
        CESAttributeContainer& ces_smattr = ces_sm.attributes();
        string material_name = ces_sm.getMaterial();
        Material material;

        res_mgr.getMaterial(material, material_name);
        material.useProfile(ces_mesh.hasSkeleton() ? "SkinnedMesh" : "StaticMesh");
        for(size_t j = 0; j < ces_smattr.getAttributeCount(); ++j)
        {
            CESAttribute& attr = ces_smattr.getAttribute(j);
            switch(attr.getType())
            {
            case CES_ARRAY:
            {
                switch(attr.getArrayType())
                {
                case CES_FLOAT: MoveAttribute< DynamicArray<float>, DynamicArray<float> >(attr, material); break;
                case CES_INT: MoveAttribute< DynamicArray<int32>, DynamicArray<int32> >(attr, material); break;
                //case CES_SHORT: MoveAttribute< DynamicArray<int16>, DynamicArray<int16> >(attr, material); break;
                //case CES_BYTE: MoveAttribute< DynamicArray<int8>, DynamicArray<int8> >(attr, material); break;
                case CES_UINT: MoveAttribute< DynamicArray<uint32>, DynamicArray<uint32> >(attr, material); break;
                //case CES_USHORT: MoveAttribute< DynamicArray<uint16>, DynamicArray<uint16> >(attr, material); break;
                //case CES_UBYTE: MoveAttribute< DynamicArray<uint8>, DynamicArray<uint8> >(attr, material); break;
                case CES_VECTOR2: MoveAttribute< DynamicArray<vec2_t>, DynamicArray<Vector2> >(attr, material); break;
                case CES_VECTOR3: MoveAttribute< DynamicArray<vec3_t>, DynamicArray<Vector3> >(attr, material); break;
                case CES_VECTOR4: MoveAttribute< DynamicArray<vec4_t>, DynamicArray<Vector4> >(attr, material); break;
                case CES_QUATERNION: MoveAttribute< DynamicArray<quat_t>, DynamicArray<Quaternion> >(attr, material); break;
                case CES_DUAL_QUATERNION: MoveAttribute< DynamicArray<dquat_t>, DynamicArray<DualQuaternion> >(attr, material); break;
                // case CES_MATRIX3 // TODO: Matrix3
                case CES_MATRIX4: 
                {
                    DynamicArray<mat4_t> arr1;
                    attr.get(arr1);
                    DynamicArray<Matrix4> arr2(arr1.size());
                    memcpy(arr2.get(), arr1.get(), sizeof(mat4_t)*arr1.size());
                    material.set(attr.getName(), arr2);
                } break;
                // case CES_FILENAME: // TODO: Texture array
                default:
                    return MeshPtr();
                }
            } break;
            case CES_FLOAT: MoveAttribute<float, float>(attr, material); break;
            case CES_INT: MoveAttribute<int32, int32>(attr, material); break;
            //case CES_SHORT: MoveAttribute<int16, int16>(attr, material); break;
            //case CES_BYTE: MoveAttribute<int8, int8>(attr, material); break;
            case CES_UINT: MoveAttribute<uint32, uint32>(attr, material); break;
            //case CES_USHORT: MoveAttribute<uint16, uint16>(attr, material); break;
            //case CES_UBYTE: MoveAttribute<uint8, uint8>(attr, material); break;
            case CES_VECTOR2: MoveAttribute<vec2_t, Vector2>(attr, material); break;
            case CES_VECTOR3: MoveAttribute<vec3_t, Vector3>(attr, material); break;
            case CES_VECTOR4: MoveAttribute<vec4_t, Vector4>(attr, material); break;
            case CES_QUATERNION: MoveAttribute<quat_t, Quaternion>(attr, material); break;
            case CES_DUAL_QUATERNION: MoveAttribute<dquat_t, DualQuaternion>(attr, material); break;
            //case CES_MATRIX3: MoveAttribute<mat3_t, ???>(attr, material); break; // TODO: Matrix3
            case CES_MATRIX4:
            {
                mat4_t mat1;
                attr.get(mat1);
                Matrix4 mat2(*mat1.elem);
                material.set(attr.getName(), mat2);
            } break;
            case CES_FILENAME:
            {
                filename_t filename;
                attr.get(filename);
                TexturePtr tex = res_mgr.getTexture(filename.name);
                material.set(attr.getName(), tex);
            } break;
            default:
                return MeshPtr();
            }
        }

        Submesh sm(res_mgr.getRenderer(), material, ces_sm.getOffset(), ces_sm.getCount(), ces_mesh.hasSkeleton());
        sm_arr.push_back(sm);
    }
    MeshPtr mesh = make_aligned_shared<Mesh>(res_mgr.getRenderer(), sm_arr,
                                             ces_mesh.getPointData(),
                                             ces_mesh.getIndices(), skeleton); 
    mesh->addAnimations(animations);
    return mesh;
}
}
